package com.hxzf.mvp.commons;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.hxzf.mvp.commons.db.CrudMapper;
import com.hxzf.mvp.commons.domain.BaseEntity;
import com.hxzf.mvp.commons.tool.ReflectUtils;
import com.hxzf.mvp.commons.tool.Validate;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * CRUD通用Service
 *
 * <p>参考{@code ServiceImpl}实现。
 *
 * @author Kai
 * @date 2021/6/18 16:44
 **/
public abstract class BaseServiceImpl<M extends CrudMapper<T>, T extends BaseEntity> implements IBaseService<T> {

    protected Log log = LogFactory.getLog(getClass());

    @Autowired
    protected M baseMapper;

    /**
     * 返回全部
     *
     * <p>出于性能考虑，请谨慎使用此方法。
     *
     * @return 全部实体类对象
     */
    @Override
    @Transactional(readOnly = true)
    public List<T> listAll() {
        return baseMapper.selectList(null);
    }

    /**
     * 将实体类作为查询条件进行查询
     *
     * <p>忽略null值查询条件
     *
     * @param entity 实体类对象
     * @return 实体列表
     */
    @Override
    @Transactional(readOnly = true)
    public List<T> list(@NonNull T entity) {
        return baseMapper.selectList(new QueryWrapper<>(entity));
    }

    /**
     * 根据ID查询实体类
     *
     * @param id 实体ID
     * @return 实体类对象
     */
    @Override
    @Transactional(readOnly = true)
    public T getById(Serializable id) {
        Validate.notNull(id, "ID should not be empty!");
        return baseMapper.selectById(id);
    }

    /**
     * 根据ID查询实体类
     *
     * @param ids 实体ID列表
     * @return 实体类对象
     */
    @Override
    @Transactional(readOnly = true)
    public List<T> getByIds(Serializable... ids) {
        Validate.notEmpty(ids, "IDs should not be empty!");
        return baseMapper.selectBatchIds(Arrays.stream(ids)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet()));
    }

    /**
     * 将实体类作为查询条件进行查询
     *
     * @param entity 实体类对象
     * @return 唯一的实体类对象
     */
    @Override
    @Transactional(readOnly = true)
    public T getOne(T entity) {
        Validate.notNull(entity, "Entity should not be empty!");
        return baseMapper.selectOne(new QueryWrapper<>(entity));
    }

    /**
     * 将实体类作为查询条件进行统计
     *
     * @param entity 实体类对象
     * @return 查询统计结果
     */
    @Override
    @Transactional(readOnly = true)
    public long count(T entity) {
        Validate.notNull(entity, "Entity should not be empty!");
        return baseMapper.selectCount(new QueryWrapper<>(entity));
    }

    /**
     * 将实体类作为查询条件判断记录是否存在
     *
     * @param entity 实体类对象
     * @return 记录是否存在
     */
    @Override
    @Transactional(readOnly = true)
    public boolean exists(T entity) {
        return count(entity) > 0;
    }

    /**
     * 将ID作为查询条件判断记录是否存在
     *
     * @param id 实体类ID
     * @return 记录是否存在
     */
    @Override
    @Transactional(readOnly = true)
    public boolean exists(Serializable id) {
        return getById(id) != null;
    }

    /**
     * 持久化实体类
     *
     * <p>null字段将会被忽略
     *
     * @param entity 实体类对象
     * @return 保存结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(T entity) {
        Validate.notNull(entity, "Entity should not be empty!");
        return baseMapper.insert(entity) > 0;
    }

    /**
     * 批量保存（MySQL专用）
     *
     * <p>注意：null值不会被忽略。
     * <p>避免长事务，每次批量插入数量不要过多。
     *
     * @param entities 实体类对象列表
     * @return 保存结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveBatch(List<T> entities) {
        Validate.containsNoNulls(entities, "Entity list should not be empty!");
        return baseMapper.insertBatchSomeColumn(entities) == entities.size();
    }

    /**
     * 修改实体
     *
     * @param entity 实体类对象
     * @return 修改结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean modify(T entity) {
        Validate.notNull(entity, "Entity should not be empty!");
        Long id = entity.getId();
        Validate.notNull(id, "ID should not be empty!");
        TableInfo tableInfo = TableInfoHelper.getTableInfo(getEntityType());
        if (tableInfo.isWithVersion()) {
            T before = baseMapper.selectById(id);
            Validate.notNull(before, "Invalid ID " + id + "!");
            // 首先获取乐观锁字段名
            String versionFieldName = tableInfo.getVersionFieldInfo().getField().getName();
            // 获取乐观锁字段值，缺省为1
            Object versionFieldValue = ObjectUtils.defaultIfNull(ReflectUtils.getFieldValue(before, versionFieldName), 1L);
            ReflectUtils.setFieldValue(entity, versionFieldName, versionFieldValue);
        }
        return baseMapper.updateById(entity) > 0;
    }

    /**
     * 新增或修改Entity
     *
     * @param entity 实体类对象
     * @return 新增/修改结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrModify(T entity) {
        return entity.getId() == null ? save(entity) : modify(entity);
    }

    /**
     * 根据ID进行删除
     *
     * @param id 实体类ID
     * @return 删除结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean remove(Serializable id) {
        Validate.notNull(id, "ID should not be empty!");
        return baseMapper.deleteById(id) > 0;
    }

    /**
     * 根据ID列表进行批量删除
     *
     * <p>注意：所有ID均必须有效。
     *
     * @param ids 实体类ID列表
     * @return 删除结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Serializable... ids) {
        Validate.notEmpty(ids, "IDs should not be empty!");
        int count = baseMapper.deleteBatchIds(Arrays.stream(ids)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet()));
        return count > 0;
    }

    @SuppressWarnings("unchecked")
    private Class<T> getEntityType() {
        ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
        return (Class<T>) parameterizedType.getActualTypeArguments()[1];
    }
}
